package Thread;

import java.util.PriorityQueue;

//这个类用于表示待做的任务
class MyTimerTask1 implements Comparable<MyTimerTask1> {
    // 定义一个时间，表明在什么时间来执行这个任务
    // 此处约定这个 time 是一个 ms 级别的时间戳
    private long time;

    // 实际任务要执行的代码，即要执行的任务
    private Runnable runnable;

    // 获取队列元素所设定的时间
    public long getTime() {
        return time;
    }

    // 因为Timer类里面的构造方法有两个参数，一个是要执行的代码，一个是指定执行时间
    // 所以我们这个任务的构造方法也模仿标准库的
    // 上面的time是一个时间戳，但是这里的delay我们期望的是一个 “相对时间间隔”
    // 即 “过多长时间后” 执行该代码；以当前时间为基准，计算 “delay” 时间后，执行代码
    public MyTimerTask1(Runnable runnable, long delay) {
        this.runnable = runnable;
        // time的赋值是：获取当前时间 + delay  就能得到我们想要的效果：
        // 假设我们是13:00：00设置的，期望3s过后执行代码，那么time此时获取的时间就是 13:00:00 + 00:00:03 = 13:00:03
        // 那么达到 13:00:03后，就会执行代码
        this.time = System.currentTimeMillis() + delay;
        // System.currentTimeMillis()方法就是用来获取当前时间的
    }

    public void run() {
        // 后续执行任务就靠 runnable 来调用
        // 这个属于回调函数
        runnable.run();
    }

    @Override
    public int compareTo(MyTimerTask1 o) {
        return (int) (this.time - o.time);
    }
}

class MyTimer1 {
    // 负责扫描任务队列，执行任务的线程
    private Thread t = null;

    // 任务队列
    // 优先级队列存储待做任务，小跟堆的方式，让定时器只关注堆顶元素即可
    // 队列里面存储的是任务，所以我们需要创建一个类，去描述这个任务
    private PriorityQueue<MyTimerTask1> queue = new PriorityQueue();

    // 给一个锁对象
    private Object locker = new Object();

    public void schedule(Runnable runnable, long delay) {
        // schedule方法用来将任务添加到优先级队列中
        // 所以第一步 1.首先要有一个任务
        // 2.将任务添加进优先级队列
        synchronized (locker) {
            MyTimerTask1 task = new MyTimerTask1(runnable, delay);
            queue.offer(task);

            // 添加进队列后，就要唤醒要执行代码的线程
            locker.notify();
        }
    }

    public void cancel() {
        t.interrupt();
    }

    // 构造方法，创建扫描线程，让扫描线程来完成判断和执行
    public MyTimer1() {
        t = new Thread(() -> {
            // 这个线程就反复扫描堆顶元素，判断时间是不是到了
            // 如果时间没到，暂时先啥也不干
            // 如果时间到了，就执行这个任务，并把这个任务从队列中删掉
            while (!Thread.currentThread().isInterrupted()) {
                synchronized (locker) {
                    try {
                        while (queue.isEmpty()) {
                            // 如果队列为空，暂时先不处理
                            // 所以这里就要添加 wait，如果线程为空的话，这个线程就要等待其他线程来提供资源，即添加元素进入队列
                            // 注意，wait之后再醒来就是沧海桑田，不能做if一锤子买卖，所以要把if改为while
                            locker.wait();
                        }

                        MyTimerTask1 task = queue.peek();
                        // 获取当前时间，与堆顶元素相比
                        long curTime = System.currentTimeMillis();
                        if (curTime >= task.getTime()) {
                            // 如果当前时间大于或等于设定时间，就执行
                            queue.poll();
                            task.run();
                        } else {
                            // 当前时间还没到，暂时先不处理
                            // 这里也需要 wait，如果不wait，假设 curTime 是13:00:00
                            // 任务时间 time 是 14：00:00，当前时间还没到呢，所以就要跳转到这里执行continue
                            // 但是continue之后，又进入while循环，又进入continue，这种情况我们称之为“忙等“
                            // 虽然在等，但是很忙，有点像我去医院取DR片，隔0.01秒（实际上这个循环可能更快）我就去取一次
                            // 但是DR片还没生成呢，还需要等1个小时呢，但是我隔0.01秒就去，不仅占用了资源（其他人用不了）
                            // 我还会很忙（CPU一直在忙，一直在调用资源），所以称为 “忙等”，你得把那个机器（资源）让出来
                            // 让其他人也能用，等到你的时间到了，你再去取
                            // 那么使用sleep可以吗？不行！sleep不能释放锁，相当于我在取DR的机器前面，霸占了这个机器
                            // 谁都别想用，这就不行呀
                            // 因为上下都要有 try catch处理异常，所以应该把这个移到外面
                            // Thread.sleep(task.getTime() - curTime);
                            locker.wait(task.getTime() - curTime);

                            // 写于这个代码结束之后：
                            // 现在这个代码还存在线程安全问题，需要加锁
                            // 问题是，加锁的目的是什么？
                            // 再次复习线程安全问题所导致的原因：修改同一变量
                            // 在这个场景下，多线程修改的是 队列 里面的元素，所以加锁，要将 队列 这个变量，保护起来
                            // 也就是说，什么时候该加锁，锁应该加在哪里？
                            // 应该加在共享资源处，不涉及到共享资源的修改，就可以放在 synchronized 外面
                        }
                        // 加锁之后，我们里面的是 while 死循环，所以这个线程是一直在拿锁，一直在占用资源
                        // 会导致其他线程饿死
                        // 所以要添加 wait
                    } catch (InterruptedException e) {
                        break;
                    }
                }
            }
        });

        t.start();
    }
}


public class ThreadDemo23 {

    public static void main(String[] args) throws InterruptedException {
        MyTimer1 myTimer = new MyTimer1();

        myTimer.schedule(() -> {
            System.out.println("time:3000");
        },3000);

        myTimer.schedule(() -> {
            System.out.println("time:2000");
        },2000);

        myTimer.schedule(() -> {
            System.out.println("time:1000");
        },1000);

        Thread.sleep(5000);
        myTimer.cancel();

        System.out.println("main");
    }
}
